/* 
 *  Copyright 2012 CodeMagi, Inc.
 * 
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 *  This product includes software developed by the
 *  Apache Software Foundation (http://www.apache.org/).
 *  The Apache Software Foundation in no way endorses or
 *  promotes this product.
 */
package com.codemagi.taglibs.input;

import com.codemagi.util.OrderedTable;
import com.codemagi.util.Utils;
import java.io.*;
import java.util.*;
import javax.servlet.jsp.*;
import org.apache.log4j.Logger;

/**
 * This class includes several utility functions used by various input
 * tags.  Functionality common to several classes is located here on
 * the relatively renegade premise that building variability into a design
 * is better than using even single inheritance.  (For example, the interfaces
 * to all utility functions is clearly outlined here, and the utility
 * functions don't have access to private members of the "interesting"
 * classes.)  I'll defend that this is more straightforward than a base
 * class that includes these any day.
 *
 * @version 1.0
 * @author Shawn Bayern for Apache Software Foundation (Original Author)
 * @author August Detlefsen for CodeMagi, Inc.
 */
class Util  {

    static Logger log = Logger.getLogger("com.codemagi.taglibs.input.Util");

    /**
     * Print any HTML tag attributes that might have been passed to a StringBuffer.
     * 
     * @param out          StringBuffer to append attributes to
     * @param attributes   Map containing attribute values
     */
    public static void printAttributes(StringBuffer out, Map attributes)
	throws JspTagException {
	
	if (attributes != null) {
	    
	    // check for illegal keys
	    if ( attributes.containsKey("name") ||
		 attributes.containsKey("value") ||
		 attributes.containsKey("type") ||
		 attributes.containsKey("checked") ) {
		throw new JspTagException("illegal key found in attributes Map");
            }

	    //iterate the attributes and print them
	    Iterator i = attributes.keySet().iterator();
	    while (i.hasNext()) {
		Object oKey = i.next();
		Object oVal = attributes.get(oKey);

		/*
		 * If the attribute contains non-Strings, give the user
		 * a more meaningful message than what he or she would get
		 * if we just propagated a ClassCastException back.
		 * (This'll get caught below.)
		 */
		if (!(oKey instanceof String) || 
		    (oVal != null && !(oVal instanceof String)) ) {
		    throw new JspTagException("all members in attributes Map must be Strings");
		}

		String key   = (String)oKey;
		String value = (String)oVal;

		// print the key (and value if appropriate)
		if (value == null) {
		    out.append("    " + quote(key) + "\n");
		} else {
		    out.append("    " + quote(key) + "=\"" + quote(value) + "\"\n");
		}
	    }
	}
    }



    /** 
     * Print any HTML tag attributes that might have been passed to the JSP output. 
     * 
     * @param out          JspWriter to append attributes to
     * @param attributes   Map containing attribute values
     */
    public static void printAttributes(JspWriter out, Map attributes)
	throws JspTagException, IOException {

	StringBuffer output = new StringBuffer();

	printAttributes(output, attributes);

	out.print(output.toString());
	
    }


    /**
     * Returns the key for an attribute, regardless of case. For example, if the input to this method 
     * is equal to 'style' and the key 'STYLE' is contained within the attributes Map, 'STYLE' will be returned.
     */
    public static String getKeyIgnoreCase(String input, Map map) {
	if (Utils.isEmpty(input)) return "";
	if (map == null) return input;

	Iterator i = map.keySet().iterator();
	while (i.hasNext()) {
	    try {
		String key = (String)i.next();

		if (input.equalsIgnoreCase(key)) return key;

	    } catch (ClassCastException cce) {
		continue;
	    }
	}

	return input;
    }


    /** 
     * Quote metacharacters in HTML. 
     */
    public static String quote(String x) {

	/*
	log.debug("ABOUT TO ESCAPE: " + x);

	x = StringEscapeUtils.escapeHtml(x);

	log.debug("X, ESCAPED: " + x);

	return x;
	*/

        if (x == null) return "";

	// deal with ampersands first 
	StringBuffer output = new StringBuffer( x );

        int index               = 0;
        int targetLength        = 1;
        int replaceLength       = 5;

        while ( true ) {
            index = output.toString().indexOf("&", index);

            if (index == -1) break;  //no match found

	    //match found, check for &#
	    if ( output.length() > index+1 && '#' != output.charAt(index+1) ) {
		output.replace(index, index + targetLength, "&amp;");
		index += replaceLength;

	    } else {
		index += targetLength;
	    }
        }

	x = output.toString();

	int c, oldC = -1;
	while ((c = x.indexOf('"')) != -1)
	    x = new String((new StringBuffer(x)).replace(c, c+1, "&quot;"));
	while ((c = x.indexOf('<')) != -1)
	    x = new String((new StringBuffer(x)).replace(c, c+1, "&lt;"));
	while ((c = x.indexOf('>')) != -1)
	    x = new String((new StringBuffer(x)).replace(c, c+1, "&gt;"));
	return x;
    }


    //PACKAGE CONSTANTS

    public static final OrderedTable DAYS = new OrderedTable();
    static {
	DAYS.put("", "Day");
	DAYS.put("01", "01");
	DAYS.put("02", "02");
	DAYS.put("03", "03");
	DAYS.put("04", "04");
	DAYS.put("05", "05");
	DAYS.put("06", "06");
	DAYS.put("07", "07");
	DAYS.put("08", "08");
	DAYS.put("09", "09");
	DAYS.put("10", "10");
	DAYS.put("11", "11");
	DAYS.put("12", "12");
	DAYS.put("13", "13");
	DAYS.put("14", "14");
	DAYS.put("15", "15");
	DAYS.put("16", "16");
	DAYS.put("17", "17");
	DAYS.put("18", "18");
	DAYS.put("19", "19");
	DAYS.put("20", "20");
	DAYS.put("21", "21");
	DAYS.put("22", "22");
	DAYS.put("23", "23");
	DAYS.put("24", "24");
	DAYS.put("25", "25");
	DAYS.put("26", "26");
	DAYS.put("27", "27");
	DAYS.put("28", "28");
	DAYS.put("29", "29");
	DAYS.put("30", "30");
	DAYS.put("31", "31");
    }


    public static final OrderedTable MONTHS = new OrderedTable();
    static {
	MONTHS.put("","Month");
	MONTHS.put("01", "01");
	MONTHS.put("02", "02");
	MONTHS.put("03", "03");
	MONTHS.put("04", "04");
	MONTHS.put("05", "05");
	MONTHS.put("06", "06");
	MONTHS.put("07", "07");
	MONTHS.put("08", "08");
	MONTHS.put("09", "09");
	MONTHS.put("10", "10");
	MONTHS.put("11", "11");
	MONTHS.put("12", "12");
    }


    public static final OrderedTable YEARS = new OrderedTable();
    static {
        YEARS.put("","Year");
        YEARS.put("1998", "1998");
        YEARS.put("1999", "1999");
        YEARS.put("2000", "2000");
        YEARS.put("2001", "2001");
        YEARS.put("2002", "2002");
        YEARS.put("2003", "2003");
        YEARS.put("2004", "2004");
        YEARS.put("2005", "2005");
        YEARS.put("2006", "2006");
        YEARS.put("2007", "2007");
        YEARS.put("2008", "2008");
        YEARS.put("2009", "2009");
        YEARS.put("2010", "2010");
        YEARS.put("2011", "2011");
        YEARS.put("2012", "2012");
        YEARS.put("2013", "2013");
        YEARS.put("2014", "2014");
        YEARS.put("2015", "2015");
        YEARS.put("2016", "2016");
        YEARS.put("2017", "2017");
        YEARS.put("2018", "2018");
        YEARS.put("2019", "2019");
        YEARS.put("2020", "2020");
    }


    public static final OrderedTable HOURS = new OrderedTable();
    static {
        HOURS.put("","Hour");
        HOURS.put("00", "00");
        HOURS.put("01", "01");
        HOURS.put("02", "02");
        HOURS.put("03", "03");
        HOURS.put("04", "04");
        HOURS.put("05", "05");
        HOURS.put("06", "06");
        HOURS.put("07", "07");
        HOURS.put("08", "08");
        HOURS.put("09", "09");
        HOURS.put("10", "10");
        HOURS.put("11", "11");
        HOURS.put("12", "12");
        HOURS.put("13", "13");
        HOURS.put("14", "14");
        HOURS.put("15", "15");
        HOURS.put("16", "16");
        HOURS.put("17", "17");
        HOURS.put("18", "18");
        HOURS.put("19", "19");
        HOURS.put("20", "20");
        HOURS.put("21", "21");
        HOURS.put("22", "22");
        HOURS.put("23", "23");
    }


    public static final OrderedTable MINUTES_BY_FIVE = new OrderedTable();
    static {
        MINUTES_BY_FIVE.put("","Min");
        MINUTES_BY_FIVE.put("00", "00");
        MINUTES_BY_FIVE.put("05", "05");
        MINUTES_BY_FIVE.put("10", "10");
        MINUTES_BY_FIVE.put("15", "15");
        MINUTES_BY_FIVE.put("20", "20");
        MINUTES_BY_FIVE.put("25", "25");
        MINUTES_BY_FIVE.put("30", "30");
        MINUTES_BY_FIVE.put("35", "35");
        MINUTES_BY_FIVE.put("40", "40");
        MINUTES_BY_FIVE.put("45", "45");
        MINUTES_BY_FIVE.put("50", "50");
        MINUTES_BY_FIVE.put("55", "55");
    }


    public static final OrderedTable TIME_ZONES = new OrderedTable();
    static {
        TIME_ZONES.put("","Time Zone");
        TIME_ZONES.put("(GMT +12:00) Aukland, Wellington", "12.0");
        TIME_ZONES.put("(GMT +11:00) Solomon Is., New Caledonia", "11.0");
        TIME_ZONES.put("(GMT +10:00) Melbourne, Sydney", "10.0");
        TIME_ZONES.put("(GMT +9:00) Tokyo, Seoul", "9.0");
        TIME_ZONES.put("(GMT +8:00) Beijing, Hong Kong, Singapore", "8.0");
        TIME_ZONES.put("(GMT +7:00) Bangkok, Jakarta", "7.0");
        TIME_ZONES.put("(GMT +6:00) Dhaka, Novosibirsk", "6.0");
        TIME_ZONES.put("(GMT +5:00) Islamabad, Karachi", "5.0");
        TIME_ZONES.put("(GMT +4:00) Abu Dhabi, Baku", "4.0");
        TIME_ZONES.put("(GMT +3:00) Moscow, Kuwait, Nairobi", "3.0");
        TIME_ZONES.put("(GMT +2:00) Athens, Istanbul, Minsk", "2.0");
        TIME_ZONES.put("(GMT +1:00) Berlin, Rome", "1.0");
        TIME_ZONES.put("(GMT) Greenwich Mean Time", "0.0");
        TIME_ZONES.put("(GMT -1:00) Azores, Cape Verde Is.", "-1.0");
        TIME_ZONES.put("(GMT -2:00) Mid-Atlantic", "-2.0");
        TIME_ZONES.put("(GMT -3:00) Brasilia, Buenos Aries", "-3.0");
        TIME_ZONES.put("(GMT -4:00) Atlantic Time", "-4.0");
        TIME_ZONES.put("(GMT -5:00) Eastern Time", "-5.0");
        TIME_ZONES.put("(GMT -6:00) Central Time", "-6.0");
        TIME_ZONES.put("(GMT -7:00) Mountain Time", "-7.0");
        TIME_ZONES.put("(GMT -8:00) Pacific Time", "-8.0");
        TIME_ZONES.put("(GMT -9:00) Alaska Time", "-9.0");
        TIME_ZONES.put("(GMT -10:00) Hawaii Time", "-10.0");
        TIME_ZONES.put("(GMT -11:00) Midway Island, Samoa", "-11.0");
    }

}
